﻿using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace BaseTool
{
    /// <summary>
    /// 投影仪工具类
    /// </summary>
    public class ProjectorTool
    {
        /// <summary> 保存文件名称 </summary>
        public string SaveFileName = "Projector.xml";
        /// <summary> 投影仪参数组 </summary>
        public ProjectorParam Param = new ProjectorParam();

        /// <summary> 是否连接 </summary>
        public bool IsOpen { get { return serialPort.IsOpen; } }

        /// <summary> 接收数据回传 </summary>
        public Action<byte[]> DataReceivedAction;

        /// <summary> 接口 </summary>
        private SerialPort serialPort = new SerialPort();
        /// <summary> 锁：发送内容 </summary>
        private readonly object lockSend = new object();
        /// <summary> 最后发送时间 </summary>
        private DateTime lastSendTime = DateTime.Now;

        #region 构造、析构、参数读取&保存
        /// <summary>
        /// 构造
        /// </summary>
        public ProjectorTool()
        {
            serialPort.DataReceived += DataReceived;
            serialPort.PinChanged += PinChanged;
        }

        /// <summary>
        /// 析构
        /// </summary>
        ~ProjectorTool()
        {
            serialPort.DataReceived -= DataReceived;
            serialPort.PinChanged -= PinChanged;
            Close();
        }

        /// <summary>
        /// 读取投影仪参数
        /// </summary>
        /// <param name="ProjectorXml">读取路径，空为Alltype中的配置</param>
        /// <returns></returns>
        public bool Read(string ProjectorXml = null)
        {
            try
            {
                string path = string.IsNullOrWhiteSpace(ProjectorXml) ? PathTool.CurrentProductPath + SaveFileName : ProjectorXml;
                if (!File.Exists(path))
                {
                    Param = new ProjectorParam();
                    Save();
                }

                Param = (ProjectorParam)FileTool.ReadXML(path, typeof(ProjectorParam));
                Log.SaveLog($"读取投影仪参数成功", LogType.Run, Color.Black);
                return true;
            }
            catch (Exception ex)
            {
                Log.SaveError(ex, $"读取投影仪参数异常，{ex.Message}", LogType.Run);
                return false;
            }
        }

        /// <summary>
        /// 写入投影仪参数
        /// </summary>
        /// <param name="ProjectorXml"></param>
        /// <returns></returns>
        public bool Save(string ProjectorXml = null)
        {
            try
            {
                string path = string.IsNullOrWhiteSpace(ProjectorXml) ? PathTool.CurrentProductPath + SaveFileName : ProjectorXml;
                bool isok = FileTool.WriteXML(path, Param);
                Log.SaveLog($"保存投影仪参数{(isok ? "成功" : "失败")}", LogType.Run, isok ? Color.Black : Color.Red);
                return isok;
            }
            catch (Exception ex)
            {
                Log.SaveError(ex, $"保存投影仪参数异常，{ex.Message}", LogType.Run);
                return false;
            }
        }
        #endregion

        #region 串口通信
        /// <summary>
        /// 获取电脑当前可用串口并添加到选项列表中
        /// </summary>
        /// <returns></returns>
        public List<string> GetPortNames()
        {
            return System.IO.Ports.SerialPort.GetPortNames().ToList();
        }

        /// <summary>
        /// 打开串口
        /// </summary>
        public void Open()
        {
            try
            {
                serialPort.PortName = Param.serialPortParam.PortName;
                serialPort.BaudRate = Param.serialPortParam.BaudRate;
                serialPort.Parity = Param.serialPortParam.Parity;
                serialPort.DataBits = Param.serialPortParam.DataBits;
                serialPort.StopBits = Param.serialPortParam.StopBits;

                serialPort.Open();

                Log.SaveLog($"打开串口（{serialPort.PortName}）成功", LogType.Sensor, Color.Gray);
            }
            catch (Exception ex)
            {
                Log.SaveError(ex, $"打开串口异常，{ex.Message}", LogType.Sensor);
            }
        }

        /// <summary>
        /// 关闭串口
        /// </summary>
        public void Close()
        {
            try
            {
                if (!IsOpen) return;
                serialPort.Close();
                Log.SaveLog($"关闭串口（{serialPort.PortName}）成功", LogType.Sensor, Color.Gray);
            }
            catch (Exception ex)
            {
                Log.SaveError(ex, $"关闭串口异常，{ex.Message}", LogType.Sensor);
            }
        }

        /// <summary>
        /// 发送信息
        /// </summary>
        public bool Send(string message)
        {
            try
            {
                if (!IsOpen)
                {
                    Log.SaveLog($"投影仪发送消息失败，投影仪未连接", LogType.Sensor, Color.Red);
                    return false;
                }

                lock (lockSend)
                {
                    SendDelay();
                    serialPort.Write(message);
                    Log.SaveLog($"投影仪发送消息：[{message}]", LogType.Sensor, Color.Gray);
                    return true;
                }
            }
            catch (Exception ex)
            {
                Log.SaveError(ex, $"投影仪发送消息异常，{ex.Message}", LogType.Sensor);
                return false;
            }
            finally
            {
                lastSendTime = DateTime.Now;
            }
        }
        /// <summary>
        /// 发送信息
        /// </summary>
        public bool Send(byte[] bytes)
        {
            try
            {
                if (!IsOpen)
                {
                    Log.SaveLog($"投影仪发送数据失败，投影仪未连接", LogType.Sensor, Color.Red);
                    return false;
                }

                lock (lockSend)
                {
                    SendDelay();
                    serialPort.Write(bytes, 0, bytes.Length);
                    Log.SaveLog($"投影仪发送数据：[{(ToHex(bytes))}]", LogType.Sensor, Color.Gray);
                    return true;
                }
            }
            catch (Exception ex)
            {
                Log.SaveError(ex, $"投影仪发送数据异常，{ex.Message}", LogType.Sensor);
                return false;
            }
            finally
            {
                lastSendTime = DateTime.Now;
            }
        }

        /// <summary>
        /// 发送延时
        /// </summary>
        public void SendDelay()
        {
            while (new TimeSpan(DateTime.Now.Ticks - lastSendTime.Ticks).TotalMilliseconds <=
                Param.serialPortParam.SendDelayTime)
                Thread.Sleep(1);
        }

        /// <summary>
        /// 接收数据回传
        /// </summary>
        private void DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            int BytesToRead = serialPort.BytesToRead;
            byte[] bytes = new byte[BytesToRead];
            serialPort.Read(bytes, 0, BytesToRead);
            DataReceivedAction?.Invoke(bytes);
            Log.SaveLog($"投影仪接收到数据：[{ToHex(bytes)}]", LogType.Sensor, false);
        }
        /// <summary>
        /// 串口变化
        /// </summary>
        private void PinChanged(object sender, SerialPinChangedEventArgs e)
        {
            if (e.EventType == SerialPinChange.DsrChanged && !serialPort.DsrHolding)
                Close();
        }
        #endregion

        #region 数据转换
        /// <summary>
        /// 字符串转十六进制
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        private string ToHex(string message)
        {
            char[] vs = message.ToCharArray();
            string b = "";
            byte[] c = new byte[message.Length];
            for (int i = 0; i < message.Length; i++)
            {
                c[i] = (byte)vs[i];
            }
            foreach (byte c1 in c)
            {
                b += c1.ToString("X2");
                b += " ";
            }
            return b;
        }
        /// <summary>
        /// byte数组转十六进制
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        private string ToHex(byte[] bytes)
        {
            string b = "";
            foreach (byte _b in bytes)
            {
                b += _b.ToString("X2");
                b += " ";
            }
            return b;
        }
        #endregion

        #region 图卡指令
        /// <summary>
        /// 01显示图卡
        /// 23-28 （0x17-0x1c） 不能修改
        /// 23 17 内部图卡（小十字架）
        /// 24 18 内部图卡（大十字架）
        /// 25 19 内部图卡（散斑）
        /// 26 1a 内部图卡（三线大十字架）
        /// 27 1b 内部图卡（全黑）
        /// 28 1c 内部图卡（棋盘格）
        /// </summary>
        /// <param name="index">图卡编号</param>
        /// <returns></returns>
        public byte[] GetData_DisplayImage(byte index = 00)
        {
            byte[] bytes = new byte[10];
            //握手指令
            bytes[0] = 0x54;
            bytes[1] = 0x80;
            //显示图卡命令
            bytes[2] = 0x01;
            //图卡编号
            bytes[3] = index;
            //备用
            bytes[4] = 0x00;
            bytes[5] = 0x00;
            bytes[6] = 0x00;
            bytes[7] = 0x00;
            bytes[8] = 0x00;
            bytes[9] = 0x00;
            return bytes;
        }

        /// <summary>
        /// 02亮度调整
        /// </summary>
        /// <param name="intensity"> 亮度强度 0-249 </param>
        /// <returns></returns>
        public byte[] GetData_BrightnessSetting(byte intensity)
        {
            if (intensity > 249)
                throw new Exception($"The brightness parameter exceeds the range. It should be set within the range of 0 to 249.");

            byte[] bytes = new byte[10];
            //握手指令
            bytes[0] = 0x54;
            bytes[1] = 0x80;
            //亮度调整命令
            bytes[2] = 0x02;
            //亮度强度
            bytes[3] = intensity;
            //备用
            bytes[4] = 0x00;
            bytes[5] = 0x00;
            bytes[6] = 0x00;
            bytes[7] = 0x00;
            bytes[8] = 0x00;
            bytes[9] = 0x00;
            return bytes;
        }

        /// <summary>
        /// 04工作模式
        /// </summary>
        /// <param name="testMode"> true测试模式 / false普通模式 </param>
        /// <param name="autoPower"> true自动断电 / false不自动断电 </param>
        /// <returns></returns>
        public byte[] GetData_WorkPattern(bool testMode, bool autoPower)
        {
            byte[] bytes = new byte[10];
            //握手指令
            bytes[0] = 0x54;
            bytes[1] = 0x80;
            //工作模式命令
            bytes[2] = 0x04;
            //工作模式
            bytes[3] = (byte)((testMode ? 0xF0 : 0x00) + (autoPower ? 0x00 : 0x0F));
            //备用
            bytes[4] = 0x00;
            bytes[5] = 0x00;
            bytes[6] = 0x00;
            bytes[7] = 0x00;
            bytes[8] = 0x00;
            bytes[9] = 0x00;
            return bytes;
        }

        /// <summary>
        /// 07连续显示图卡
        /// </summary>
        /// <param name="trigger"> 连续触发信息 </param>
        /// <returns></returns>
        public byte[] GetData_ContinuousDisplayImage(ProjectorConTrigger trigger)
        {
            return GetData_ContinuousDisplayImage(trigger.Start, trigger.Frame, trigger.Count);
        }
        /// <summary>
        /// 07连续显示图卡
        /// </summary>
        /// <param name="start">开始图卡编号</param>
        /// <param name="frame">帧率</param>
        /// <param name="count">图片数量</param>
        /// <returns></returns>
        public byte[] GetData_ContinuousDisplayImage(byte start, ProjectorFrame frame, byte count)
        {
            byte[] bytes = new byte[10];
            //握手指令
            bytes[0] = 0x54;
            bytes[1] = 0x80;
            //连续显示图卡命令
            bytes[2] = 0x07;
            //图卡编号
            bytes[3] = start;
            bytes[4] = frame.Data;
            bytes[5] = count;

            //备用
            bytes[6] = 0x00;
            bytes[7] = 0x00;
            bytes[8] = 0x00;
            bytes[9] = 0x00;
            return bytes;
        }

        /// <summary>
        /// 08开关LED灯
        /// </summary>
        /// <param name="open"> true开 / false关</param>
        /// <returns></returns>
        public byte[] GetData_SwitchLED(bool open)
        {
            byte[] bytes = new byte[10];
            //握手指令
            bytes[0] = 0x54;
            bytes[1] = 0x80;
            //开关LED灯命令
            bytes[2] = 0x08;
            //开 / 关
            bytes[3] = open ? (byte)0x01 : (byte)0x00;
            //备用
            bytes[4] = 0x00;
            bytes[5] = 0x00;
            bytes[6] = 0x00;
            bytes[7] = 0x00;
            bytes[8] = 0x00;
            bytes[9] = 0x00;
            return bytes;
        }

        /// <summary>
        /// 传入图像自动分辨竖条纹图像或者横条纹图像
        /// </summary>
        /// <param name="image">传入图像</param>
        /// <param name="index">图卡编号</param>
        /// <param name="isErrorReturnAllWhite"></param>
        /// <returns></returns>
        public byte[] GetData_GenStripe(Mat image, byte index, bool isErrorReturnAllWhite = false)
        {
            //1 初始化判断
            if (image == null || image.Height == 0 || image.Width == 0)
            {
                if (isErrorReturnAllWhite)
                    return GetData_GenSolidColor(255);
                else
                    throw new Exception("The image is null.");
            }

            //2 简单宽高判断
            if (image.Height == 1 && image.Width >= 1)
                return GetData_GenVerticalStripe(index, image);
            else if (image.Height >= 1 && image.Width == 1)
                return GetData_GenHorizontalStripe(index, image);

            //3 复杂判断
            else if (image.Height > 1 && image.Width > 1)
            {
                if (IsVerticalStripe(image))
                    return GetData_GenVerticalStripe(index, image);
                if (IsHorizontalStripe(image))
                    return GetData_GenHorizontalStripe(index, image);
            }

            //4 图像非条纹图像
            if (isErrorReturnAllWhite)
                return GetData_GenSolidColor(255);
            else
                throw new Exception("This image is not a striped image.");
        }
        /// <summary>
        /// 传入图像数据自动分辨竖条纹图像或者横条纹图像
        /// </summary>
        /// <param name="imageData">传入图像数据</param>
        /// <param name="index">图卡编号</param>
        /// <param name="width"> 宽 </param>
        /// <param name="height"> 高 </param>
        /// <returns></returns>
        public byte[] GetData_GenStripe(byte[] imageData, byte index, int width, int height)
        {
            //1 初始化判断
            if (imageData == null || imageData.Length == 0)
                throw new Exception("The image is null.");

            //2 简单宽高判断
            if (imageData.Length == width)
                return GetData_GenVerticalStripe(index, imageData);
            else if (imageData.Length == height)
                return GetData_GenHorizontalStripe(index, imageData);

            //3 图像数据出错
            throw new Exception("This image is not a striped image.");
        }
        /// <summary>
        /// 纯色背景
        /// </summary>
        /// <param name="index">图卡编号</param>
        /// <param name="gray">纯色数值，255白，0黑</param>
        /// <returns></returns>
        public byte[] GetData_GenSolidColor(byte index, byte gray = 255)
        {
            // 0 - 命令 - 6 | 7 - 有效数据 - 967 | 968 - 无效数据（补齐） - 1287 
            //数据开始索引
            int start = 7;
            //数据总长度
            int count = 1280 + 7;
            //投影仪有效数据长度
            int validCount = 960;

            //返回数据
            byte[] bytes = new byte[count];

            //握手指令
            bytes[0] = 0x54;
            bytes[1] = 0x80;
            //条纹命令
            bytes[2] = 0x09;
            //图卡编号
            bytes[3] = index;
            //竖条纹
            bytes[4] = 0xFF;
            bytes[5] = 0xFF;
            bytes[6] = 0xFF;

            //填充有效数据
            byte[] validData = Enumerable.Repeat(gray, validCount).ToArray();
            Array.Copy(validData, 0, bytes, start, validData.Length);

            //填充无效数据（补齐）
            byte[] invalidData = Enumerable.Repeat((byte)255, count - validCount - start).ToArray();
            Array.Copy(invalidData, 0, bytes, validCount + start, invalidData.Length);

            return bytes;
        }
        /// <summary>
        /// 竖条纹
        /// </summary>
        /// <param name="index">图卡编号</param>
        /// <param name="stripe">条纹图像</param>
        /// <returns></returns>
        public byte[] GetData_GenVerticalStripe(byte index, Mat stripe)
        {
            //提取图像数据
            byte[] stripeData;
            stripe.Row(0).GetArray(out stripeData);

            return GetData_GenVerticalStripe(index, stripeData);
        }
        /// <summary>
        /// 竖条纹
        /// </summary>
        /// <param name="index">图卡编号</param>
        /// <param name="stripeData">图像数据</param>
        /// <returns></returns>
        public byte[] GetData_GenVerticalStripe(byte index, byte[] stripeData)
        {
            // 0 - 命令 - 6 | 7 - 有效数据 - 967 | 968 - 无效数据（补齐） - 1287 
            //数据开始索引
            int start = 7;
            //数据总长度
            int count = 1280 + 7;
            //投影仪有效数据长度
            int validCount = 960;

            //返回数据
            byte[] bytes = new byte[count];

            //握手指令
            bytes[0] = 0x54;
            bytes[1] = 0x80;
            //条纹命令
            bytes[2] = 0x09;
            //图卡编号
            bytes[3] = index;
            //竖条纹
            bytes[4] = 0xFF;
            bytes[5] = 0xFF;
            bytes[6] = 0xFF;

            //填充有效数据
            validCount = Math.Min(validCount, stripeData.Length);
            Array.Copy(stripeData, 0, bytes, start, validCount);

            //填充无效数据（补齐）
            byte[] invalidData = Enumerable.Repeat((byte)255, count - validCount - start).ToArray();
            Array.Copy(invalidData, 0, bytes, validCount + start, invalidData.Length);

            return bytes;
        }
        /// <summary>
        /// 横条纹
        /// </summary>
        /// <param name="index">图卡编号</param>
        /// <param name="stripe">条纹图像</param>
        /// <returns></returns>
        public byte[] GetData_GenHorizontalStripe(byte index, Mat stripe)
        {
            //提取图像数据
            byte[] stripeData;
            stripe.Col(0).GetArray(out stripeData);

            return GetData_GenHorizontalStripe(index, stripeData);
        }
        /// <summary>
        /// 横条纹
        /// </summary>
        /// <param name="index">图卡编号</param>
        /// <param name="stripeData">图像数据</param>
        /// <returns></returns>
        public byte[] GetData_GenHorizontalStripe(byte index, byte[] stripeData)
        {
            // 0 - 命令 - 9 | 10 - 有效数据 - 550 | 968 - 无效数据（补齐） - 1287 
            //数据开始索引
            int start = 7 + 3;
            //数据总长度
            int count = 1280 + 7;
            //投影仪有效数据长度
            int validCount = 540;

            //返回数据
            byte[] bytes = new byte[count];

            //握手指令
            bytes[0] = 0x54;
            bytes[1] = 0x80;
            //条纹命令
            bytes[2] = 0x09;
            //图卡编号
            bytes[3] = index;
            //横条纹
            bytes[4] = 0xFF;
            bytes[5] = 0xFF;
            bytes[6] = 0xFF;
            bytes[7] = 0x00;
            bytes[8] = 0x01;
            bytes[9] = 0x02;

            //填充有效数据
            validCount = Math.Min(validCount, stripeData.Length);
            Array.Copy(stripeData, 0, bytes, start, validCount);

            //填充无效数据（补齐）
            byte[] invalidData = Enumerable.Repeat((byte)255, count - validCount - start).ToArray();
            Array.Copy(invalidData, 0, bytes, validCount + start, invalidData.Length);

            return bytes;
        }
        /// <summary>
        /// 是否是竖条纹图像
        /// </summary>
        /// <param name="mat"></param>
        /// <returns></returns>
        private bool IsVerticalStripe(Mat mat)
        {
            for (int col = 0; col < mat.Cols; col++)
            {
                byte value = mat.At<byte>(0, col);
                for (int row = 0; row < mat.Rows; row++)
                {
                    if (mat.At<byte>(row, col) != value)
                        return false;
                }
            }
            return true;
        }
        /// <summary>
        /// 是否是横条纹图像
        /// </summary>
        /// <param name="mat"></param>
        /// <returns></returns>
        private bool IsHorizontalStripe(Mat mat)
        {
            for (int row = 0; row < mat.Rows; row++)
            {
                byte value = mat.At<byte>(row, 0);
                for (int col = 0; col < mat.Cols; col++)
                {
                    if (mat.At<byte>(row, col) != value)
                        return false;
                }
            }
            return true;
        }

        /// <summary>
        /// 获取图像数据
        /// </summary>
        /// <param name="image"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public byte[] GenStripe(Mat image)
        {
            //1 初始化判断
            if (image == null || image.Height == 0 || image.Width == 0)
                throw new Exception("The image is null.");

            byte[] stripeData = new byte[0];

            //2 简单宽高判断
            if (image.Height == 1 && image.Width >= 1)
                image.Row(0).GetArray(out stripeData);
            else if (image.Height >= 1 && image.Width == 1)
                image.Col(0).GetArray(out stripeData);

            //3 复杂判断
            else if (image.Height > 1 && image.Width > 1)
            {
                if (IsVerticalStripe(image))
                    image.Row(0).GetArray(out stripeData);
                if (IsHorizontalStripe(image))
                    image.Col(0).GetArray(out stripeData);
            }

            //4 图像非条纹图像
            if (stripeData == null || stripeData.Length == 0)
                throw new Exception("This image is not a striped image.");
            else
                return stripeData;
        }
        #endregion
    }
}
